<?php
class CF_Acl
{
	/**
	 * 
	 * @var array
	 */
	protected $_roles = array();
	
	/**
	 * @var array
	 */
	protected $_resources = array();
	
	/**
	 * @var array
	 */
	protected $_rules = array();
	
	/**
	 * @var boolean|CF::TYPE_EXCEPTION
	 */
	protected $_allowOnNotFound = false;
	
	/**
	 * @return void
	 */
	public function __construct()
	{
		
	}
	
	/**
	 * @return boolean|CF::TYPE_EXCEPTION
	 */
	public function getAllowOnNotFound()
	{
		return $this->_allowOnNotFound;
	}
	
	/**
	 * 
	 * @param boolean|CF::TYPE_EXCEPTION $cond
	 * @return CF_Acl_Resource $this
	 */
	public function setAllowOnNotFound($cond)
	{
		$this->_allowOnNotFound = (CF::TYPE_EXCEPTION === $cond)? $cond : (boolean) $cond;
		return $this;
	}
	
	/**
	 * 
	 * @param string|CF_Acl_Role $role
	 */
	public function hasRole($role)
	{
		return isset($this->_roles[(string) $role]);
	}
	
	/**
	 * 
	 * @param CF_Acl_Role|string $role
	 * @return CF_Acl_Role
	 */
	public function getRole($role)
	{
		$id = (string) $role;
		return $this->hasRole($id)? $this->_roles[$id] : null;
	}
	
	/**
	 * 
	 * @param CF_Acl_Role|string $role
	 * @return CF_Acl $this
	 * @throws CF_Acl_Exception
	 */
	public function addRole($role)
	{
		$id = (string) $role;
		if($this->hasRole($id)) return $this;

		if(is_string($role)) $role = new CF_Acl_Role($id, $this);
		
		if(!($role instanceof CF_Acl_Role)){
			throw new CF_Acl_Exception("Role \"$id\" must be a instance of CF_Acl_Role");
		}
		
		$this->_roles[$id] = $role;
		return $this;
	}
	
	/**
	 * 
	 * @param string|CF_Acl_Role $role
	 * @return CF_Acl $this
	 */
	public function removeRole($role)
	{
		unset($this->_roles[(string) $role]);
		return $this;
	}
	
	/**
	 * 
	 * @param string|CF_Acl_Resource $resource
	 * @return boolean
	 */
	public function hasResource($resource)
	{
		return isset($this->_resources[(string) $resource]);
	}
	
	/**
	 * 
	 * @param CF_Acl_Resourcestring $resource
	 * @return CF_Acl_Resource
	 */
	public function getResource($resource)
	{
		$id = (string) $resource;
		return $this->hasResource($resource)? $this->_resources[$id] : null;
	}
	
	/**
	 * 
	 * @param string|CF_Acl_Resource $resource
	 * @return CF_Acl $this
	 */
	public function addResource($resource)
	{
		$id = (string) $resource;
		if($this->hasResource($id)) return $this;
		
		if(is_string($resource)) $resource = new CF_Acl_Resource($id, $this);
		
		if(!($resource instanceof CF_Acl_Resource)){
			throw new CF_Acl_Exception('Resource must be a instance of CF_Acl_Resource');
		}
		
		$this->_resources[$id] = $resource;
		return $this;	
	}
	
	/**
	 * 
	 * @param boolean $type
	 * @param string|array|CF_Acl_Role $roles [optional]
	 * @param string|array|CF_Acl_Resource $resources [optional]
	 * @param string|array $privileges [optional]
	 * @return CF_Acl $this
	 */
	public function setRule($type, $roles = null, $resources = null, $privileges = null)
	{
		if(null === $roles) $roles			= array_keys($this->_roles);
		if(null === $resources) $resources	= array_keys($this->_resources);
		
		if(!is_array($resources))$resources	= array((string) $resources);
		
		foreach ($resources as $resource){
			$resource = $this->getResource((string) $resource);
			if(!$resource) continue;
			
			$resource->setRule($type, $roles, $privileges);
		}
		
		return $this;
	}
	
	/**
	 * 
	 * @param string|array|CF_Acl_Role $roles [optional]
	 * @param string|array|CF_Acl_Resource $resources [optional]
	 * @param string|array $privileges [optional]
	 * @return CF_Acl::setRule()
	 */
	public function allow($roles = null, $resources = null, $privileges = null)
	{
		return $this->setRule(true, $roles, $resources, $privileges);
	}
	
	/**
	 * 
	 * @param string|array|CF_Acl_Role $roles [optional]
	 * @param string|array|CF_Acl_Resource $resources [optional]
	 * @param string|array $privileges [optional]
	 * @return CF_Acl::setRule()
	 */
	public function deny($roles = null, $resources = null, $privileges = null)
	{
		return $this->setRule(false, $roles, $resources, $privileges);
	}
	
	/**
	 * 
	 * @param string|array|CF_Acl_Role $roles [optional]
	 * @param string|array|CF_Acl_Resource $resources [optional] 
	 * @param string|array $privileges
	 * @return boolean
	 * @throws CF_Acl_Exception
	 */
	public function isAllowed($roles = null, $resources = null, $privileges = null)
	{
		# if $roles is null, check all roles
		if(null === $roles) $roles				= $this->_roles;
		if(!is_array($roles)) $roles			= array($roles);
		
		# if resource is null, check all resources
		if(null === $resources) $resources		= $this->_resources;
		if(!is_array($resources)) $resources	= array($resources);
		
		# in case array of resource
		# return true if all resource return true
		foreach($resources as $resource){
			$resourceId = (string) $resource;
			$resource = $this->getResource($resourceId);
			
			# if resource not found
			# return $this->_allowOnNotFound or throw Exception
			if(null === $resource){
				if(CF::TYPE_EXCEPTION === $this->_allowOnNotFound){
					throw new CF_Acl_Exception("Resource \"$resourceId\" not found");
				}
				if(true !== $this->_allowOnNotFound) return $this->_allowOnNotFound;
			}
			
			if(true !== ($res = $resource->isAllowed($roles, $privileges))) return $res;
		}
		
		return true;
	}
}